﻿using Dijiang.Values;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Dijiang
{
    public class CmdParser
    {
        static readonly string SYMBOL_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._+-*/\\<>!?&=";
        static readonly string UNESCAPABLE_CHARACTERS = "abfnrtv\\\'\"";
        public static DValue ParseCmd(string src)
        {
            var expr = new DExpression();
            int pos = 0;
            while (pos < src.Length)
            {
                DValue val = Read(src, ref pos);
                if (val is DError err) return err;

                expr.Add(val);
            }

            return expr;
        }

        public static DValue Parse(string src)
        {
            int pos = 0;
            return Read(src, ref pos);
        }

        public static DValue Read(string src, ref int pos)
        {
            while (pos < src.Length && char.IsWhiteSpace(src[pos]))
            {
                pos++;
            }

            DValue val;
            if (pos >= src.Length)
            {
                return new DError("Unexcepted EOF");
            }
            else if (src[pos] == '(')
            {
                pos++;
                val = ReadExpr(src, ref pos);
            }
            else if (src[pos] == '[')
            {
                pos++;
                val = ReadList(src, ref pos);
            }
            else if (src[pos] == '"')
            {
                val = ReadString(src, ref pos);
            }
            else if (src[pos] == '@')
            {
                pos++;
                val = ReadSymbol(src, ref pos, true);
            }
            else if (SYMBOL_CHARACTERS.Contains(src[pos]))
            {
                val = ReadSymbol(src, ref pos);
            }
            else
            {
                val = new DError("Unexcepted character: " + src[pos]);
            }

            while (pos < src.Length && char.IsWhiteSpace(src[pos]))
            {
                pos++;
            }

            return val;
        }

        public static DValue ReadExpr(string src, ref int pos)
        {
            var expr = new DExpression();
            while (pos < src.Length && src[pos] != ')')
            {
                DValue val = Read(src, ref pos);
                if (val is DError err) return err;

                expr.Add(val);

                if (pos >= src.Length)
                {
                    return new DError("Syntax error.");
                }
            }
            pos++;
            return expr;
        }

        public static DValue ReadList(string src, ref int pos)
        {
            var list = new DList();
            while (pos < src.Length && src[pos] != ']')
            {
                var val = Read(src, ref pos);
                if (val is DError err) return err;

                list.Add(val);

                if (pos >= src.Length)
                {
                    return new DError("Syntax error.");
                }
            }
            pos++;
            return list;
        }

        public static DValue ReadSymbol(string src, ref int pos, bool quoted = false)
        {
            var builder = new StringBuilder();

            while (pos < src.Length && SYMBOL_CHARACTERS.Contains(src[pos]))
            {
                builder.Append(src[pos]);
                pos++;
            }

            var symbol = builder.ToString();

            if (bool.TryParse(symbol, out bool b))
            {
                return new DBoolean(b);
            }
            else if (int.TryParse(symbol, out int inum))
            {
                return new DNumber(inum);
            }
            else if (float.TryParse(symbol, out float fnum))
            {
                return new DNumber(fnum);
            }
            else
            {
                return new DSymbol(symbol, quoted);
            }
        }

        private static char GetUnescapeChar(char x)
        {
            switch (x)
            {
                case 'a': return '\a';
                case 'b': return '\b';
                case 'f': return '\f';
                case 'n': return '\n';
                case 'r': return '\r';
                case 't': return '\t';
                case 'v': return '\v';
                case '\\': return '\\';
                case '\'': return '\'';
                case '\"': return '\"';
            }
            return '\0';
        }

        public static DValue ReadString(string src, ref int pos)
        {
            var builder = new StringBuilder();

            pos++;

            while (pos < src.Length && src[pos] != '"')
            {
                char c = src[pos];

                if (c == '\\')
                {
                    pos++;
                    if (UNESCAPABLE_CHARACTERS.Contains(src[pos]))
                    {
                        c = GetUnescapeChar(src[pos]);
                    }
                    else
                    {
                        return new DError("Invalid escape seq \\" + src[pos]);
                    }
                }

                builder.Append(c);
                pos++;
                if (pos >= src.Length)
                {
                    return new DError("Syntax error.");
                }
            }

            // pass final "
            pos++;

            return new DString(builder.ToString());
        }

        public static bool IsBrackets(char ch)
        {
            return ch == '(' || ch == ')' || ch == '[' || ch == ']';
        }

        public static bool IsStringQuote(char ch)
        {
            return ch == '"';
        }
    }
}
